package IO;

//InputStream就是Java标准库提供的最基本的输入流。它位于java.io这个包里。java.io包提供了所有同步IO的功能。
//要特别注意的一点是，InputStream并不是一个接口，而是一个抽象类，它是所有输入流的超类。这个抽象类定义的一个最重要的方法就是int read()，签名如下：
//
//public abstract int read() throws IOException;
//
//这个方法会读取输入流的下一个字节，并返回字节表示的int值（0~255）。如果已读到末尾，返回-1表示不能继续读取了。
//
//FileInputStream是InputStream的一个子类。顾名思义，FileInputStream就是从文件流中读取数据。下面的代码演示了如何完整地读取一
//个FileInputStream的所有字节：
//
//public void readFile() throws IOException {
//    // 创建一个FileInputStream对象:
//    InputStream input = new FileInputStream("src/readme.txt");
//    for (;;) {
//        int n = input.read(); // 反复调用read()方法，直到返回-1
//        if (n == -1) {
//            break;
//        }
//        System.out.println(n); // 打印byte的值
//    }
//    input.close(); // 关闭流
//}
//在计算机中，类似文件、网络端口这些资源，都是由操作系统统一管理的。应用程序在运行的过程中，如果打开了一个文件进行读写，完成后要及时地关闭，以便让操作系统把资源释
//放掉，否则，应用程序占用的资源会越来越多，不但白白占用内存，还会影响其他应用程序的运行。
//InputStream和OutputStream都是通过close()方法来关闭流。关闭流就会释放对应的底层资源。
//我们还要注意到在读取或写入IO流的过程中，可能会发生错误，例如，文件不存在导致无法读取，没有写权限导致写入失败，等等，这些底层错误由Java虚拟机自动封装
//成IOException异常并抛出。因此，所有与IO操作相关的代码都必须正确处理IOException。
//仔细观察上面的代码，会发现一个潜在的问题：如果读取过程中发生了IO错误，InputStream就没法正确地关闭，资源也就没法及时释放。
//因此，我们需要用try ... finally来保证InputStream在无论是否发生IO错误的时候都能够正确地关闭：
//
//public void readFile() throws IOException {
//    InputStream input = null;
//    try {
//        input = new FileInputStream("src/readme.txt");
//        int n;
//        while ((n = input.read()) != -1) { // 利用while同时读取并判断
//            System.out.println(n);
//        }
//    } finally {
//        if (input != null) { input.close(); }
//    }
//}
//用try ... finally来编写上述代码会感觉比较复杂，更好的写法是利用Java 7引入的新的try(resource)的语法，只需要编写try语句，让编译器自动为我们关闭资
//源。推荐的写法如下：
//
//public void readFile() throws IOException {
//    try (InputStream input = new FileInputStream("src/readme.txt")) {
//        int n;
//        while ((n = input.read()) != -1) {
//            System.out.println(n);
//        }
//    } // 编译器在此自动为我们写入finally并调用close()
//}
//实际上，编译器并不会特别地为InputStream加上自动关闭。编译器只看try(resource = ...)中的对象是否实现了java.lang.AutoCloseable接口，如果实现了，
//就自动加上finally语句并调用close()方法。InputStream和OutputStream都实现了这个接口，因此，都可以用在try(resource)中。
//
//《缓冲》
//在读取流的时候，一次读取一个字节并不是最高效的方法。很多流支持一次性读取多个字节到缓冲区，对于文件和网络流来说，利用缓冲区一次性读取多个字节效率往往要高很
//多。InputStream提供了两个重载方法来支持读取多个字节：
//
//--int read(byte[] b)：读取若干字节并填充到byte[]数组，返回读取的字节数
//--int read(byte[] b, int off, int len)：指定byte[]数组的偏移量和最大填充数
//
//利用上述方法一次读取多个字节时，需要先定义一个byte[]数组作为缓冲区，read()方法会尽可能多地读取字节到缓冲区， 但不会超过缓冲区的大小。read()方法的返回值
//不再是字节的int值，而是返回实际读取了多少个字节。如果返回-1，表示没有更多的数据了。
//
//利用缓冲区一次读取多个字节的代码如下：
//
//public void readFile() throws IOException {
//    try (InputStream input = new FileInputStream("src/readme.txt")) {
//        // 定义1000个字节大小的缓冲区:
//        byte[] buffer = new byte[1000];
//        int n;
//        while ((n = input.read(buffer)) != -1) { // 读取到缓冲区
//            System.out.println("read " + n + " bytes.");
//        }
//    }
//}
//
//《阻塞》
//在调用InputStream的read()方法读取数据时，我们说read()方法是阻塞（Blocking）的。它的意思是，对于下面的代码：
//
//int n;
//n = input.read(); // 必须等待read()方法返回才能执行下一行代码
//int m = n;
//执行到第二行代码时，必须等read()方法返回后才能继续。因为读取IO流相比执行普通代码，速度会慢很多，因此，无法确定read()方法调用到底要花费多长时间。
//
//《InputStream实现类》
//用FileInputStream可以从文件获取输入流，这是InputStream常用的一个实现类。此外，ByteArrayInputStream可以在内存中模拟一个InputStream：

//import java.io.*;
//public class 使用InputStream {
//    public static void main(String[] args) throws IOException {
//        byte[] data = { 72, 101, 108, 108, 111, 33 };
//        try (InputStream input = new ByteArrayInputStream(data)) {
//            int n;
//            while ((n = input.read()) != -1) {
//                System.out.println((char)n);
//            }
//        }
//        
//        try (InputStream input1 = new ByteArrayInputStream(data)) {
//        	int n1;
//            byte[] buffer=new byte[4];
//            while ((n1=input1.read(buffer)) !=-1) {
//            	System.out.println("read " + n1 + " bytes.");
//            }
//        }
//    }
//}

//ByteArrayInputStream实际上是把一个byte[]数组在内存中变成一个InputStream，虽然实际应用不多，但测试的时候，可以用它来构造一个InputStream。
//举个栗子：我们想从文件中读取所有字节，并转换成char然后拼成一个字符串，可以这么写：

//public class Main {
//    public static void main(String[] args) throws IOException {
//        String s;
//        try (InputStream input = new FileInputStream("C:\\test\\README.txt")) {
//            int n;
//            StringBuilder sb = new StringBuilder();
//            while ((n = input.read()) != -1) {
//                sb.append((char) n);
//            }
//            s = sb.toString();
//        }
//        System.out.println(s);
//    }
//}
//要测试上面的程序，就真的需要在本地硬盘上放一个真实的文本文件。如果我们把代码稍微改造一下，提取一个readAsString()的方法：

//public class Main {
//    public static void main(String[] args) throws IOException {
//        String s;
//        try (InputStream input = new FileInputStream("C:\\test\\README.txt")) {
//            s = readAsString(input);
//        }
//        System.out.println(s);
//    }
//
//    public static String readAsString(InputStream input) throws IOException {
//        int n;
//        StringBuilder sb = new StringBuilder();
//        while ((n = input.read()) != -1) {
//            sb.append((char) n);
//        }
//        return sb.toString();
//    }
//}
//对这个String readAsString(InputStream input)方法进行测试就相当简单，因为不一定要传入一个真的FileInputStream：

import java.io.*;
public class 使用InputStream {
    public static void main(String[] args) throws IOException {
        byte[] data = { 72, 101, 108, 108, 111, 33 };
        try (InputStream input = new ByteArrayInputStream(data)) {
            String s = readAsString(input);
            System.out.println(s);
        }
    }

    public static String readAsString(InputStream in) throws IOException {
        int n;
        StringBuilder sb = new StringBuilder();
        while ((n = in.read()) != -1) {
            sb.append((char) n);
        }
        return sb.toString();
    }
}

//这就是面向抽象编程原则的应用：接受InputStream抽象类型，而不是具体的FileInputStream类型，从而使得代码可以处理InputStream的任意实现类。
//
//《小结》
//Java标准库的java.io.InputStream定义了所有输入流的超类：
//FileInputStream实现了文件流输入；
//ByteArrayInputStream在内存中模拟一个字节流输入。
//总是使用try(resource)来保证InputStream正确关闭。
